/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.example.android.apis.graphics;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.*;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.hardware.Camera;
import android.hardware.Camera.Size;
import android.hardware.Camera.PictureCallback;
import android.hardware.Camera.PreviewCallback;
import android.hardware.Camera.ShutterCallback;
import android.os.Bundle;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.SubMenu;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.Window;
import android.view.WindowManager;
import android.widget.SeekBar;
import android.widget.Toast;

import com.example.android.apis.R;
import com.example.android.apis.view.Grid2;
import com.example.android.apis.view.SeekBar1;

// ----------------------------------------------------------------------



public class CameraPreview extends Activity {    
    private Preview mPreview;
    private int matchingMode = 0;
    private int matchImage,matchIntensity = 100,mIntensity = 100;
    private double doub = 1.0;
    private Bitmap pictMatch;
    
    @Override
	protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);        
        // Hide the window title.
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 
        					 WindowManager.LayoutParams.FLAG_FULLSCREEN);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        
        // Create our Preview view and set it as the content of our activity.
        mPreview = new Preview(this);
        setContentView(mPreview); 
    }
    
    @Override
    public boolean onCreateOptionsMenu(Menu m){
    	
    	SubMenu MainMenu = m.addSubMenu(0,0,0,"Match Mode");
    	MainMenu.setHeaderTitle("Matching Mode:");
    	MainMenu.setGroupCheckable(0, true, true);
    	m.addSubMenu(0,1,1,"Effect Intensity");
    	m.addSubMenu(0,2,2,"Other Option");
    	MainMenu.add(1,3,0,"Camera Exposure");
    	MainMenu.add(1,4,1,"Stored Picture");
    	
    	return super.onCreateOptionsMenu(m);
    }
    
    @Override
    public boolean onOptionsItemSelected(MenuItem i){
	    	int s = i.getItemId();	
	    	
	    	if (s == 1){
	    		Bundle b = new Bundle();
	    		b.putDouble("C_I",doub);
	    		Log.d("Camera Demo","Set current intensity"+doub);
	    		Intent mBlend = new Intent(this.getApplicationContext(),SeekBar1.class);
	    		
	    		mBlend.putExtras(b);
	    		this.startActivityForResult(mBlend,matchIntensity);
;	    	}
	    	if (s == 3)
	    		mPreview.setMatchingMode(0);
	    	if (s == 4){
	    		Intent pictSelect = new Intent(this,Grid2.class);
	    		this.startActivityForResult(pictSelect,matchImage);
	    	}    		
    	return super.onOptionsItemSelected(i);
    }
    private void showMsg(String m){
    	Toast msg = Toast.makeText(this,m,Toast.LENGTH_LONG);
    	msg.show();
    }
    private void showMsg(int m){
    	Toast msg = Toast.makeText(this,m,Toast.LENGTH_LONG);
    	msg.show();
    }
    
    protected void onActivityResult(int reqC, int resC, Intent data)	{
    	Log.d("CheckStartActivity","onActivityResult and resultCode = "+resC);
    	
    	super.onActivityResult(reqC, resC, data);
    	if(resC == 1)	{
    		Bundle b = data.getExtras();
    		matchImage = b.getInt("IMAGE_SELECT");
    		mPreview.setMatchingMode(1);
    		mPreview.setPictMatch(matchImage);
    		showMsg(matchImage);      		
    	}
    	else if (resC == 2){
    		Bundle bun = data.getExtras();
    		doub = bun.getDouble("E_INT");
    		mPreview.setEffectIntensity(doub);
    		//showMsg(doub.to)
    		showMsg(bun.toString());
    		//showMsg(matchIntensity);
    	}
    	
    	else
    		showMsg("Result Not Received!");
    }
}


class Preview extends SurfaceView implements SurfaceHolder.Callback, PreviewCallback  {
    SurfaceHolder mHolder;
    Camera mCamera;
    private Bitmap lock,unLock, previewFrame,savedFrame,pictMatch;
    java.io.InputStream is;
    
    private DisplayMetrics metrics;
    private Canvas c;
    private int prevW, prevH,screenW, screenH, matchingMode = 0;
    private static double mIntensity = 1.0;
    private int pW = 320, pH = 240, bW = 82, bH = 106, bMargR = 15, cW, cH;
    private double imDist, cHistRed, cHistGreen, cHistBlue, cHistGray;
    
    private Camera.Parameters parameters;
    private Size previewSize,pictureSize;
    private int[] pixels, modifiedPix,savedPixels,modSavedPixels,pictPixels;
    private int[] mapR = new int[256];
    private int[] mapG = new int[256];
    private int[] mapB = new int[256];
    private int[] mRHist = new int[256], mGHist = new int[256], mBHist = new int[256], mGrayHist = new int[256];
    private int[] redHist = new int[256];
    private int[] greenHist = new int[256];
    private int[] blueHist = new int[256];
    //private int[] grayHist = new int[256];
    
    private boolean effectsOn = false, histMatched = false,showSavedFrame = false;
    
    private Context cText;
    private Toast notifyOn, notifyOff;
    private CharSequence matchON = "Matching On", matchOFF = "Matching Off";
    int dur = Toast.LENGTH_SHORT;
    
    
    Preview(Context context) {
        super(context);
        cText = context;
        is = context.getResources().openRawResource(R.drawable.lock);
        lock = BitmapFactory.decodeStream(is);
        is = context.getResources().openRawResource(R.drawable.unlock);
        unLock = BitmapFactory.decodeStream(is); 
        
        // Install a SurfaceHolder.Callback so we get notified when the
        // underlying surface is created and destroyed.
        mHolder = getHolder();
        mHolder.addCallback(this);
        //mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
        mHolder.setType(SurfaceHolder.SURFACE_TYPE_NORMAL);
        metrics = new DisplayMetrics();
        screenW = metrics.widthPixels;
        screenH = metrics.heightPixels;
    }
    
    
   

    public void surfaceCreated(SurfaceHolder holder) {
        // The Surface has been created, acquire the camera and tell it where
        // to draw.
        mCamera = Camera.open();
        try {
           mCamera.setPreviewCallback(this);
           
           parameters = mCamera.getParameters();
           parameters.setPreviewSize(pW,pH);
           parameters.setPreviewFrameRate(15);
           parameters.setSceneMode(Camera.Parameters.SCENE_MODE_NIGHT);
           parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
           
           previewSize = parameters.getPreviewSize();
           mCamera.setParameters(parameters);
           //Log.i("Something","Preview size is: " + previewSize.width + "," + previewSize.height);
           pixels = new int[previewSize.width * previewSize.height];
           modifiedPix = new int[previewSize.width * previewSize.height];
           prevW = previewSize.width;
           prevH = previewSize.height;
           
        } catch (Exception exception) {
            mCamera.release();
            mCamera = null;
            // TODO: add more exception handling logic here
        }
    }

    public void surfaceDestroyed(SurfaceHolder holder) {
        // Surface will be destroyed when we return, so stop the preview.
        // Because the CameraDevice object is not a shared resource, it's very
        // important to release it when the activity is paused.
        mCamera.setPreviewCallback(null);
    	mCamera.stopPreview();
        mCamera.release();
        mCamera = null;
    }

    public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
        // Now that the size is known, set up the camera parameters and begin
        // the preview.
        Camera.Parameters parameters = mCamera.getParameters();
        parameters.setPreviewSize(pW,pH);
        parameters.setPreviewFrameRate(15);
        parameters.setSceneMode(Camera.Parameters.SCENE_MODE_AUTO);
        parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO);
        parameters.setPictureFormat(4);
        parameters.setPictureSize(640,480);
        
        mCamera.setParameters(parameters);
        mCamera.startPreview();
    }
    
    ShutterCallback sCB = new ShutterCallback()	{
    	public void onShutter()	{
    		Log.d("Camera Demo","onShutter d");
    	}
    };
    
    PictureCallback rCB = new PictureCallback()	{
    	public void onPictureTaken(byte[] data, Camera cam){
    		Log.d("Camera Demo","onPictureTaken - raw");
    	}
    };
    
    PictureCallback jCB = new PictureCallback()	{
    	public void onPictureTaken(byte[] data,Camera cam){
    		
    		savedFrame = BitmapFactory.decodeByteArray(data, 0, data.length);
    		savedPixels = new int[savedFrame.getWidth() * savedFrame.getHeight()];    		
    		savedFrame.getPixels(savedPixels, 0,savedFrame.getWidth(),0,0,savedFrame.getWidth(),savedFrame.getHeight());
    		modSavedPixels = new int[savedPixels.length];
    		intXFormRGB(savedPixels,mapR,mapG,mapB,modSavedPixels);
    		savedFrame = Bitmap.createBitmap(modSavedPixels, savedFrame.getWidth(),savedFrame.getHeight(),Config.RGB_565);    		
    		FileOutputStream outStream = null;
    		
    		try {
    			File file = new File(String.format("/sdcard/image%d.png",System.currentTimeMillis()));
    			outStream = new FileOutputStream(file);
    			
    			if (effectsOn)
    				savedFrame.compress(Bitmap.CompressFormat.PNG, 100,outStream);
    			else
    				outStream.write(data);
    			
    			outStream.flush();
    			outStream.close();
    			
    		} catch (FileNotFoundException e){
    			e.printStackTrace();
    		} catch (IOException e) {
    			e.printStackTrace();
    		} finally {
    			
    		}
    		mCamera.startPreview();
    	}
    };
    
    public void setMatchingMode(int mode){
    	matchingMode = mode;
    }
    
    public void setPictMatch(int pict)	{
    	Resources res = getResources();  
    	BitmapFactory.Options bO = new BitmapFactory.Options();
    	bO.inSampleSize = 4;
    	pictMatch = BitmapFactory.decodeResource(res, pict,bO);
    	Log.d("Camera Demo","Matched Image Loaded"+pictMatch.toString());
    	histMatched = false;
    }
    
    public void setEffectIntensity(double e){
    	mIntensity = e;
    }
    
    @Override
    public void onPreviewFrame(byte[] data, Camera camera)	{    	
    	
    	decodeYUV420SP(pixels,data,previewSize.width,previewSize.height);
    	
    	//Get reference template to match to
    	if (effectsOn && !histMatched)	{
	    	if (matchingMode == 0)	{    		
	    		imHist(pixels,mRHist,previewSize.width,previewSize.height,0);
		    	imHist(pixels,mGHist,previewSize.width,previewSize.height,1);
		    	imHist(pixels,mBHist,previewSize.width,previewSize.height,2);
	    	}
	    	
	    	if (matchingMode == 1){
	    		
	    		int w = pictMatch.getWidth();
	    		int h = pictMatch.getHeight();
	    		pictPixels = new int[w*h];
	    		pictMatch.getPixels(pictPixels, 0, w, 0, 0, w, h);
	    		imHist(pictPixels,mRHist,w,h,0);
		    	imHist(pictPixels,mGHist,w,h,1);
		    	imHist(pictPixels,mBHist,w,h,2);
		    	
	    	}
	    	//For luminance histogram
	    	//imHist(pixels,mGrayHist,previewSize.width,previewSize.height,3);
	    	histMatched = true;
    	}
    	
    	//Do histogram matching to current frame against locked frame
    	if (effectsOn && histMatched)	{

    		
    		
    		
    		imHist(pixels,redHist,previewSize.width,previewSize.height,0);
    		imHist(pixels,greenHist,previewSize.width,previewSize.height,1);
    		imHist(pixels,blueHist,previewSize.width,previewSize.height,2);
    		
    		
    		histoMatchRGB(redHist,greenHist,blueHist,
    					  mRHist ,mGHist   ,mBHist,
    					  mapR   ,mapG     ,mapB);
    					  
    		/* For just luminance matching
    		histoMatchRGB(redHist   ,greenHist   ,blueHist,
					  	  mGrayHist ,mGrayHist   ,mGrayHist,
					  	  mapR      ,mapG        ,mapB);
					  	  */
    		
    		intXFormRGB(pixels,mapR,mapG,mapB,modifiedPix);
    		
    		//Histogram stretch source image
    		
    		histoStretchRGB(modifiedPix,mapR,mapG,mapB);
    		
    		intXFormRGB(modifiedPix,mapR,mapG,mapB,modifiedPix);
    		//pixels = modifiedPix;
    	}  	
    	
    	 
    	//Create a bitmap to draw, using either the unmodified or modified
    	//preview image
    	if (effectsOn)
    		previewFrame = Bitmap.createBitmap(modifiedPix,prevW,prevH,Bitmap.Config.ARGB_8888);
    	else
    		previewFrame = Bitmap.createBitmap(pixels,prevW,prevH,Bitmap.Config.ARGB_8888);
    	
    	//Draws matched image to screen
    	c = mHolder.lockCanvas();
    	cW = c.getWidth();
    	cH = c.getHeight();
    	Rect src = new Rect(0,0,previewSize.width,previewSize.height);
    	Rect dst = new Rect(0,0,cW,cH);
    	//Log.i("Canvas width" + cW +"Canvas height" + cH, "something");
    	if (showSavedFrame)
    		c.drawBitmap(savedFrame,src,dst,null);    	
    	else
    		c.drawBitmap(previewFrame,src,dst,null);
    	
    	if(effectsOn)
    		c.drawBitmap(lock,cW - bW - bMargR , (cH/2) - (bH/2), null);  		
    	else
        	c.drawBitmap(unLock,cW - bW - bMargR , (cH/2) - (bH/2), null);
    	
    	mHolder.unlockCanvasAndPost(c);    	
    	
    	//Log.i("Pixels", "The top right pixels has the following RGB(hexadecimal) values:"
    		//	+Integer.toHexString(pixels[0]));    	
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event)	{
    	int motion = event.getAction();
    	float xCoords = event.getX(0), yCoords = event.getY(0);
    	
    	notifyOn = Toast.makeText(cText, matchON, dur);
    	notifyOff = Toast.makeText(cText, matchOFF, dur);

	    //Check to see if the lock button is pushed
    	if (motion == 1 && 
	    		xCoords >= (cW - bMargR - bW) && 
	    		xCoords <= (cW - bMargR) &&
	    		yCoords >= (cH/2 - bH/2) && 
	    		yCoords <= (cH/2 + bH/2) 
	    	)	{
    		if (!effectsOn )	{
	    		effectsOn = true;
	    		notifyOn.show();
    		}    		
	    	else {
	    		effectsOn = false;
	    		histMatched = false;
	    		notifyOff.show();
	    	}
	    }
    	//Take a picture
    	else if(motion == 1)	{
    		//mCamera.stopPreview();
    		mCamera.takePicture(sCB, rCB, jCB);
    	}
    	return true;
    }    
    
    //Method from Ketai project
    void decodeYUV420SP(int[] rgb, byte[] yuv420sp, int width, int height)	{
    	final int frameSize = width * height;
    	
    	for (int j = 0, yp = 0; j < height; j++)	{
    		int uvp = frameSize + (j >> 1) * width, u = 0, v = 0;
    		for (int i = 0; i < width; i++, yp++)	{
    			int y = (0xff & ((int) yuv420sp[yp])) - 16;
    			if (y < 0)
    				y = 0;
    			if ( (i & 1) == 0)	{
    				v = (0xff & yuv420sp[uvp++]) - 128;
    				u = (0xff & yuv420sp[uvp++]) - 128;
    			}
    			
    			int y1192 = 1192 * y;
    			int r = (y1192 + 1634 *v);
    			int g = (y1192 - 833 * v - 400 * u);
    			int b = (y1192 + 2066 * u);
    			
    			if(r < 0) r = 0; else if (r > 262143) r = 262143;
    			if(g < 0) g = 0; else if (g > 262143) g = 262143;
    			if(b < 0) b = 0; else if (b > 262143) b = 262143;
    			
    			rgb[yp] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | ((b >> 10) & 0xff);
    		}
    	}
    }
    
    static public void imHist(int[] rgb, int[] hist, int w, int h, int chan)	{
    	for (int i = 0; i < 256; i++)
    		hist[i] = 0;
    	//Red Channel
    	if (chan == 0)	{
    		for (int i = 0; i < w * h; i += 3)	{
    			int pixVal = 
    				(rgb[i] >> 16) & 0xff;
    			hist[pixVal]++;
    		}
    	}
    	
    	//Green Channel
    	else if (chan == 1)	{
    		for (int i = 1; i < (w * h)-1; i += 3)	{
    			int pixVal = (rgb[i] >> 8) & 0xff;
    			hist[pixVal]++;
    		}
    	}
    	
    	//Blue Channel
    	else if (chan == 2)	{
    		for (int i = 2; i < (w * h)-2; i += 3)	{
    			int pixVal = rgb[i] & 0xff;
    			hist[pixVal]++;
    		}
    	}
    	else	{
    		for (int i = 0; i < w * h; i += 3)	{
    			int pixVal = (
	    				( (rgb[i] >> 16) & 0xff ) +
	    				( (rgb[i] >> 8)  & 0xff ) +
	    				(  rgb[i]        & 0xff ) 
    				) / 3;
    			hist[pixVal]++;
    		}
    	}
    }
    
    //Find Cummulative Distribution Function
    //Used for Hist. Matching
    public static double[] CDF (int[] h)	{
    	int sum = 0;
    	for(int i = 0; i < h.length; i++)
    		sum += h[i];
    	double [] cdf = new double[h.length];
    	int cu = h[0];
    	cdf[0] = (double) cu / sum;
    	for (int i = 1; i < h.length; i++)	{
    		cu += h[i];
    		cdf[i] = (double) cu / sum;
    	}
    	return cdf;
    }
    
    public static void histoStretchRGB(int[] source,int[] rM,int[] gM,int[] bM){
    	int r,g,b,rmin=255,gmin=255,bmin=255,rmax=0,bmax=0,gmax=0;
    	int rmmdif,gmmdif,bmmdif;
    	double t, ratio;
    	
    	for (int i = 0; i < source.length; i++) {
    		r = (source[i] >> 16) & 0xff; g = (source[i] >> 8 ) & 0xff; b = source[i] & 0xff;
    		//r = (source[i] >> 16) & 0xff0000; g = (source[i] >> 8 ) & 0xff00; b = source[i] & 0xff;
    		
    		rmax = ( r > rmax ) ? r : rmax;
    		rmin = ( r < rmin ) ? r : rmin;
    		gmax = ( g > gmax ) ? g : gmax;
    		gmin = ( g < gmin ) ? g : gmin;
    		bmax = ( b > bmax ) ? b : bmax;
    		bmin = ( b < bmin ) ? b : bmin;
    	}
    	
    	Log.d("Red Max / Min",rmax+"/"+rmin);
    	Log.d("Gre Max / Min",gmax+"/"+gmin);
    	Log.d("Blu Max / Min",bmax+"/"+bmin);
    	
    	rmmdif = rmax - rmin; gmmdif = gmax - gmin; bmmdif = bmax - bmin;
    	for (int i = 0; i < source.length; i++){
    		r = (source[i] >> 16) & 0xff; g = (source[i] >> 8 ) & 0xff; b = source[i] & 0xff;
    		//r = (source[i] >> 16) & 0xff0000; g = (source[i] >> 8 ) & 0xff00; b = source[i] & 0xff;
    		t = r - rmin;
    		ratio = t / rmmdif;
    		rM[r] = (int) (ratio*255);
    		
    		t = g - gmin;
    		ratio = t / gmmdif;
    		gM[g] = (int) (ratio*255);
    		
    		t = b - bmin;
    		ratio = t / bmmdif;
    		bM[b] = (int) (ratio*255);    		
    	}
    }
    
    public static void histoMatchRGB(int[] rHSrc,
    								 int[] gHSrc,
    								 int[] bHSrc,
    								 int[] rHMtch,
    								 int[] gHMtch,
    								 int[] bHMtch,
    								 int[] rMap,
    								 int[] gMap,
    								 int[] bMap)	{
    	int K = rHSrc.length;
    	double[] rSrcCDF = CDF(rHSrc);
    	double[] gSrcCDF = CDF(gHSrc);
    	double[] bSrcCDF = CDF(bHSrc);
    	
    	double[] rMchCDF = CDF(rHMtch);
    	double[] gMchCDF = CDF(gHMtch);
    	double[] bMchCDF = CDF(bHMtch);
    	
    	for (int i = 0; i < K; i++)	{
    		int j = K - 1;
    		do	{
    			rMap[i] = j;
    			j--;
    		} while( j >= 0 && rSrcCDF[i] <= rMchCDF[j] );
    		
    		j = K - 1;
    		do	{
    			gMap[i] = j;
    			j--;
    		} while( j >= 0 && gSrcCDF[i] <= gMchCDF[j] );
    		
    		j = K - 1;
    		do	{
    			bMap[i] = j;
    			j--;
    		} while( j >= 0 && bSrcCDF[i] <= bMchCDF[j] );
    	}    	
    }
    
    public static void intXFormRGB(int[] f, int[] rMap, int[] gMap, int[] bMap, int[] g){
    	int red, green, blue, oldFR, oldFG, oldFB;
    	int t;
    	/*
    	int min= 255 << 24 | 255 << 16 | 255 << 8 | 255;
    	int max = 0 << 24 | 0 << 16 | 0 << 8 | 0;
    	int minMaxDiff = 0;
    		
    	for (int i = 0; i < f.length; i++){
    		max = (f[i] > max) ? f[i] : max;
    		min = (f[i] < min) ? f[i] : min;
    	}
    	
    	//minMaxDiff = max - min;	
    	*/
    	for (int i = 0; i < f.length; i++) {
    		
    		//t = f[i];    		
    		//t = t - min;    		
    		//t = (t / minMaxDiff) * 255;    		
    		
    		oldFR = (f[i] >> 16) & 0xff;
    		//oldFR = (t >> 16) & 0xff;
    		oldFG = (f[i] >> 8 ) & 0xff;
    		//oldFG = (t >> 8 ) & 0xff;
    		oldFB =  f[i] 		 & 0xff;
    		//oldFB =  t 		 & 0xff;
    		
    		//Apply weighted average of matched color and source color
    		red   = (int) ( (rMap[oldFR] * mIntensity) + (oldFR * (1-mIntensity)) );
    		green = (int) ( (gMap[oldFG] * mIntensity) + (oldFG * (1-mIntensity)) );
    		blue  = (int) ( (bMap[oldFB] * mIntensity) + (oldFB * (1-mIntensity)) );
    		
    		g[i] = 255 << 24 | red << 16 | green << 8 | blue;
    		//g[i] = 0xff000000 | ((red << 16) & 0xff0000) | ((green << 8 ) & 0xff00) | blue & 0xff;
    		//g[i] = 0xff000000 | ((red << 16) & 0xff) | ((green << 8 ) & 0xff) | blue & 0xff;
    		//g[i] = 0xff000000 | ((red << 6) & 0xff0000) | ((green >> 2 ) & 0xff00) | ((blue >> 10) & 0xff);
    	}
    }
}
